/*
 * Copyright Aristool AG Switzerland
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 * 
 * http://www.apache.org/licenses/LICENSE-2.0 
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License.
 */

dojo.require("dijit.layout.ContentPane");
dojo.require("dojo.data.ItemFileReadStore");
dojo.require("dijit.Tree");

if(typeof oppConfig == "undefined") {
  oppConfig = {};
  oppConfig.isProduction = false;
  oppConfig.isdebug = true;
  if(djConfig) {
    oppConfig.isDebug = djConfig.isDebug;
	if(djConfig.debugAtAllCosts) {
	  oppConfig.isDebug = true;
	}
  }
}

opprua = {};

if(oppConfig.isDebug) {
  dojo.addOnLoad(function(){
     opprua.cells = new opprua.widget.InfoCell();
     opprua.cells.show();
  });
}

opprua.debug = function(msg) {
  if(oppConfig.isDebug && opprua.cells) {
    opprua.cells.debug(msg);
  }
}

opprua.trackElement = function(toServer, fromServer) {
  this.toServer = toServer;
  this.fromServer = fromServer;
  this.time = new Date().toLocaleString();
}

opprua.callTrack = function(toServer, fromServer) {
  opprua.cells.callTrack(new opprua.trackElement(toServer, fromServer));
}

opprua.registry = (function() {
  this.layouts = {};
  this.idSeed = 0;

  this.dataReg = {};
  this.root = null;
  
  this.setRoot = function(r) {
    this.root = r;
  }
  
  this.regDependency = function(component) {
    if(!component) {
      if(!root) return;
      for(i in this.dataReg) {
        var c = this.dataReg[i];
        c.deps = [];
      }
      component = root;
    }
    
    var d = component.getDataPoints();
    for(i in d) {
      var c = this.dataReg[i];
      if(!c) {
        alert("Application error, requiring unavailable data: " + i);
      } else {
        var listed = false;
        for (i = 0; i < c.deps.length; i++) {
          if (c.deps[i] === obj) {
            listed = true;
            break;
          }
        }
        if(!listed)  {
          c.deps.push(component);
        }
      }
    }

    var d = component.getSubcomponents();
    for(i in d) {
      this.regDependency(d[i]);
    }
  };

  this.regData = function(dataPoint, data, tag) {
    var v = this.dataReg[dataPoint];
    if(!v) {
      v = {};
      this.dataReg[dataPoint] = v;
      v.deps = [];
    }
    v.data = data;
    v.tag = tag;
  };
  
  this.getData = function(dataPoint) {
    var v = this.dataReg[dataPoint];
    if(!v) {
      alert("Application error, requiring unavailable data: " + dataPoint);
    }
    return v.data;
  };
  
  this.dump = function() {
    if(!this.root) {
      return document.createTextNode("Nothing loaded yet");
    }
    o = [];
    this._dump("root", root, o, 0);
    
    var res = new dojo.data.ItemFileReadStore(
                     {"data":{label:"name", identifier:"id", items:o}});
    var tree = new dijit.Tree({"store": res, labelAttr: "name", 
                      query: {type:"top"}, typeAttr:"type"});
    return tree;
  }

  this._dump = function(name, comp, res, id) {

    var d = {};
    d.type = "down";
    if(id == 0) {
      d.type = "top";
    }
    d.id = "id" + id;
    d.name = name;
    
    res.push(d);
    var children = []; 
    var s = comp.getSubcomponents();
    ++id;
    for(i in s) {
      children.push({"_reference":"id"+id});
      var tit = comp.id + ": " + comp.description.name + ", a " + comp.description.decorator;
      id = this._dump(tit, s[i], res, id);
    }
    if(children.length > 0) {
      d.children = children;
    }
    return id;
  };
  
  this.fetchLayout = function(name) {
    if(!name) return "<div id=\"$id$\"></div>";

    var c = this.layouts[name];
    if(c) return c;
  
    c = dojo._getText(name);
    this.layouts[name] = c;
    return c;
  };

  this.newId = function() {
    return "oppid" + (this.idSeed++);
  }
  
  
  this.createComponent = function(desc) {
    try {
      if(desc.dojoRequire) {
        dojo.require(desc.dojoRequire);
      }
      comp = new opprua.component(desc);

      if(desc.decorator) {
        eval(desc.decorator + "(comp)");
      }
      return comp;
    } catch (e) {
      if( desc.dojoRequire != "opprua.widget.AutoLayout") {
        opprua.debug("failed to create component with " + 
                    desc.dojoRequire + " for " + desc.name + ", using auto layout instead");
        desc.dojoRequire = "opprua.widget.AutoLayout";
        desc.decorator = "opprua.autolayout";
        return this.createComponent(desc);
      } else {
        throw e;
      }
    }
  }

  return this;
})();

opprua.dumpObject = function(obj, id) {
  var o = [];
  opprua._dumpObject(id, obj, o, 0);
  
  var res = new dojo.data.ItemFileReadStore(
                     {"data":{label:"name", identifier:"id", items:o}});
  var tree = new dijit.Tree({"store": res, labelAttr: "name", 
                      query: {type:"top"}, typeAttr:"type"});
  return tree;
}

opprua._dumpObject = function(name, obj, res, id) {

  var d = {};
  d.type = "down";
  if(id == 0) {
    d.type = "top";
  }
  d.id = "id" + id;
  d.name = name + ": ";
  
  res.push(d);
  if(!obj) {
    d.name += "null";
  } else if(typeof (obj) == "string") {
    d.name += obj[i];
  } else if(typeof (obj) != "object") {
    d.name += obj;
  } else {
    var children = []; 
    ++id;
    for(i in obj) {
      children.push({"_reference":"id"+id});
      id = opprua._dumpObject(i, obj[i], res, id);
    }
    if(children.length > 0) {
      d.children = children;
    }
  }
  
  return ++id;
};

opprua.installRoot = function(component) {
  var root = dijit.byId("root");
  var n = opprua.registry.createComponent(component);
  n.install(root);
  opprua.registry.setRoot(n);
};

opprua.component = function(description) {
  this.description = description;
  this.id = opprua.registry.newId();
  
  this.getLayout = function(url) {
    return opprua.registry.fetchLayout(url).replace(/\$id\$/g, this.id);
  }
  
  this.translate = function(map, name) {  
    for(i in map) {
      if(map[i].as == name) {
        return map[i].name;
      }
    }
    alert("No mapping found for " + name);
    return null;
  }
  
  this.collect = function(map) {
    var res = []; 
    for(i in map) {
      res.push(map[i].as)
    }
    return res;
  }
  
  this.createComponent = function(name) {
    var desc = this.translate(this.description.subs, name);
    return opprua.registry.createComponent(desc);
  }
  
  this.getData = function(name) {
    var desc = this.translate(this.description.data, name);
    desc = opprua.registry.getData(desc);
    return desc;
  }
  
  this.getComponentNames = function() {
    return this.collect(this.description.subs);
  }
  
  this.getDataNames = function() {
    return this.collect(this.description.data);
  }
  
  this.getRequests = function() {
    return this.description.requests;
  }
  
  this.getWidget = function(id) {
    return dijit.byId(id);
  }
};

opprua.request = function(entry, data) {

  /**
    The request. All parameters are optional as follows:
      If no entry is given, the server will use the default entry point.
      If no data is given, an entry with no parameter is assumed. If the
            needs data (has type defined), there will be an exception.
  */
  this.entry = entry;
  this.data = data;
  
  /**
      If updates===null, all data points are checked for new data.
            otherwise exactly the data points listed will be checked.
  */
  this.updates = null;
  this.setUpdate = function(u) {
    this.update = u;
    return this;
  }
  /**
      If updates===null, all data points are checked for new data.
            otherwise exactly the data points listed will be checked.
  */
  this.errorhandler = null;
  this.setErrorHandler = function(e) {
    this.errorHandler = e;
    return this;
  }
  
  /**
    if a request is volatile, the request following it will erase it if
    that next one is issued before the volatile one is send off to server
  */
  this.volatile = false;
  this.setVolatile = function() {
    this.volatile = true;
    return this;
  }
  
  
  /**
    For requests that need component from the server, this is an extra
    call for setup. The component is the server name of the component 
    required. The mount is a function accepting a single parameter
    which will be the newly configured component.
  */  
  this.requestComponent = function(component, host, mount) {
    this.component = component;
    this.host = host;
    this.mount = mount;
  }

  this.callObj = null;  
  /**
   */
  this.addParameter = function(key, value) {
    if(!this.data) {
      this.data = {};
    }
    this.data[key] = value;
    return this;
  }
  
  this.callString = function() {
    if(! this.callObj) {
      var obj = {};

      obj.entry = this.entry;
      obj.data = this.data;

      obj.points = {};

      if(!this.updates) {
        var u = [];
        for(i in opprua.registry.dataReg) {
          u.push(i);
        }
        this.updates = u;
      }
    
      for(i in this.updates) {
        obj.points[this.updates[i]] = opprua.registry.dataReg[this.updates[i]].tag;
      }
      if(this.component) obj.component = this.component;

      this.callObj = obj;
    }
    return dojo.toJson(this.callObj);
  }

  this.send = function() {
    opprua.queue.add(this);
  }
  
  this.back = function(data) {
    opprua.callTrack(this.callObj, data);
    
    if(!data || data == "") {
      this.error("", "Server failed to deliver proper response");
      return;
    }
    
    if(data.ExceptionType) {
      this.error("", data);
      return;
    }
    
    var c = data.data;
    for(i in c) {
      opprua.registry.regData(i, c[i], "");
    }
    
    if(data.component && this.mount) {
      this.mount.call(this.host, data.component);
    }
    opprua.queue.next();
  }
  
  this.error = function(type, error) {
    opprua.queue.clear();
    var t = "";
    if(typeof error == "string") {
      t =  "Exception: " + error;
    } else {
      for(i in error) {
        t += i + ": " + error[i] + ";\n";
      }
    }
    opprua.debug(t);
    if(this.errorHandler) {
      this.errorHandler.call(error);
    } else {
      alert(t);
    }
  }
};

opprua.queue = (function() {
  this.queue = [];
  this.running = false;
  
  this.clear = function() {
    this.queue = [];
    this.running = false;
  }
  
  this.add = function(req) {
    var l = this.queue.pop();
    if(l && !l.volatile) this.queue.push(l);
    queue.push(req);
    if(!this.running) next();
  }
  
  this.next = function() {
    this.running = false;
    var l = this.queue.shift();
    if( ! l ) return;
    
    this.running = true;
    var r = l.callString();
  
    // create the actual request and activate it
    var call = dojo._xhrObj();
   	call.open('GET', "req.opp?data=" + r, true);
    call.onreadystatechange = function() {
      if(call.readyState != 4) return;
      l.back(eval("(" + call.responseText + ")"));
    };
    call.send(null);
  }
  return this;
})();
